package server

import (
	"errors"
	"net"
	"time"

	"gitee.com/crack007/goose/gsjob"
	"gitee.com/crack007/goose/gsutil"
)

type Session struct {
	conn             net.Conn
	packConn         net.PacketConn
	Server           *Server
	SessionId        int64
	sessionHandle    ISessionHandle
	readIdleTimeout  time.Duration // 读空闲超时时间
	writeIdleTimeout time.Duration // 写空闲超时时间
	idleTimeout      time.Duration // 空闲超时时间
	lastReadAt       time.Time
	lastWriteAt      time.Time
	lastActiveAt     time.Time
	readIdleJob      *SessionJob
	writeIdleJob     *SessionJob
	idleJob          *SessionJob
	RemoteIp         string
	RemotePort       uint16
	Tag              string // 会话标签，便于管理
	coder            ICoder // TODO 编码/解码器待重新设计
	sessionClose     bool
	udpAddr          net.Addr
	sessionIO        ISessionIO
}

func (x *Session) GetSessionHandle() ISessionHandle {
	return x.sessionHandle
}

func (x *Session) Conn() net.Conn {
	return x.conn
}

func (x *Session) init() error {
	addr := x.conn.RemoteAddr()
	if v, ok := addr.(*net.TCPAddr); ok {
		x.RemoteIp = v.IP.To4().String()
		if v.IP.IsLoopback() {
			x.RemoteIp = "127.0.0.1"
		}

		x.RemotePort = uint16(int16(v.Port))
	} else {
		if !x.Server.isUnix {
			x.RemoteIp, x.RemotePort = gsutil.IpUtil.ParseFromAddr(x.conn.RemoteAddr())
		} else {
			x.RemoteIp, x.RemotePort = "", uint16(time.Now().UnixNano())
		}
	}

	x.sessionHandle.Init(x)
	if !x.sessionHandle.OnHandshake(x) {
		x.Close()
		return errors.New("OnHandshake error")
	}

	x.SessionId = x.sessionHandle.GetSessionId(x)
	x.Server.SessionManager.addSession(x)
	x.sessionIO = &tcpSessionIO{conn: x.conn}
	x.sessionHandle.OnConnect(x)
	return nil
}

func (x *Session) read(data []byte) {
	// 处理读数据
	x.lastReadAt = time.Now()
	x.lastActiveAt = x.lastReadAt
	x.sessionHandle.OnMessage(x, data)
}

func (x *Session) write(data []byte) {
	err := x.sessionIO.Write(data)
	if err != nil {
		x.close(err)
		return
	}

	x.lastWriteAt = time.Now()
	x.lastActiveAt = x.lastWriteAt
}

func (x *Session) process() {
	err := x.init()
	if err != nil {
		return
	}

	x.pushJob()
	for true {
		//   读取数据
		data := make([]byte, x.Server.sessionBuilder.ReadBuffer)
		read, err := x.conn.Read(data)
		if err != nil {
			break
		}

		x.Server.MessageManager.Read(x, data[:read])
	}

	x.close(nil)
}

func (x *Session) close(err error) {
	if x.sessionClose {
		return
	}

	// 从会话列表中移除
	x.sessionClose = true
	defer func() {
		x.Server.SessionManager.removeSession(x)
		x.sessionIO.Close()
	}()

	x.sessionHandle.OnClose(x, err)
	if x.readIdleJob != nil {
		x.readIdleJob.Cancel()
	}

	if x.writeIdleJob != nil {
		x.writeIdleJob.Cancel()
	}

	if x.idleJob != nil {
		x.idleJob.Cancel()
	}
}

func (x *Session) Close() {
	x.close(nil)
}

// 投递空闲事件检测任务。
func (x *Session) pushJob() {
	if x.readIdleTimeout > 0 {
		x.readIdleJob = NewSessionJob(NewServerReadIdleTimeout(x),
			"readIdleTimeout",
			&gsjob.ScheduleConfig{Every: x.readIdleTimeout})
		x.readIdleJob.Submit()
	}

	if x.writeIdleTimeout > 0 {
		x.writeIdleJob = NewSessionJob(NewServerWriteIdleTimeout(x),
			"writeIdleTimeout",
			&gsjob.ScheduleConfig{Every: x.writeIdleTimeout})
		x.writeIdleJob.Submit()
	}

	if x.idleTimeout > 0 {
		x.idleJob = NewSessionJob(NewServerIdleTimeout(x),
			"idleTimeout",
			&gsjob.ScheduleConfig{Every: x.idleTimeout})
		x.idleJob.Submit()
	}
}

// 异步发送
func (x *Session) AsyncSend(data []byte) {
	x.Server.MessageManager.Write(x, data)
}

func (x *Session) Send(data []byte) {
	x.write(data)
}

func (x *Session) ProcessUdp(data []byte) {
	x.Server.MessageManager.Read(x, data)
}

func (x *Session) initUdp(onClose func()) {
	addr := x.udpAddr
	if v, ok := addr.(*net.UDPAddr); ok {
		x.RemoteIp = v.IP.To4().String()
		if v.IP.IsLoopback() {
			x.RemoteIp = "127.0.0.1"
		}

		x.RemotePort = uint16(int16(v.Port))
	} else {
		if !x.Server.isUnix {
			x.RemoteIp, x.RemotePort = gsutil.IpUtil.ParseFromAddr(x.conn.RemoteAddr())
		} else {
			x.RemoteIp, x.RemotePort = "", uint16(time.Now().UnixNano())
		}
	}
	x.sessionHandle.Init(x)
	x.SessionId = x.sessionHandle.GetSessionId(x)
	if !x.sessionHandle.OnHandshake(x) {
		x.Close()
		return
	}

	x.Server.SessionManager.addSession(x)
	x.pushJob()
	x.sessionIO = &udpSessionIO{conn: x.packConn, addr: x.udpAddr, onClose: onClose}
	x.sessionHandle.OnConnect(x)
}
